﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Dapper.Moon
{
    internal class SubqueryExpressionProvider
    {
        private ExpressionProvide _expression = null;

        public SubqueryExpressionProvider(ExpressionProvide expression)
        {
            _expression = expression;
        }

        public string Resolve(Expression exp)
        {
            MethodCallExpression mce = exp as MethodCallExpression;
            switch (mce.Method.Name)
            {
                case "Select":
                case "Any":
                case "NotAny":
                case "Contains":
                case "NotContains":
                    SubqueryModel subquery = new SubqueryModel();
                    var stackExp = new Stack<Expression>();
                    var currentExp = mce.Object;
                    if (mce.Method.Name == "Select" && currentExp != null && mce.Arguments.Any())
                    {
                        var args = mce.Arguments;
                        subquery.Select = _expression.Resolve(args[0]);
                    }
                    if (mce.Method.Name == "Any" || mce.Method.Name == "NotAny")
                    {
                        subquery.IsExists = mce.Method.Name == "Any";
                    }
                    if (mce.Method.Name == "Contains" || mce.Method.Name == "NotContains")
                    {
                        subquery.IsIn = mce.Method.Name == "Contains";
                        var args = mce.Arguments;
                        string firstName = (_expression.MasterExpression as LambdaExpression).Parameters.First().Name;
                        var oldVal = _expression.IsPrefix;
                        _expression.IsPrefix = true;
                        _expression.IsSubquery = true;
                        subquery.InField = _expression.Resolve(args[0]);
                        //替换外层的表别名
                        subquery.InField = subquery.InField.Replace($"{firstName}.", "t1.");
                        _expression.IsPrefix = oldVal;
                        _expression.IsSubquery = false;
                    }

                    while (currentExp != null)
                    {
                        stackExp.Push(currentExp);
                        switch (currentExp.NodeType)
                        {
                            case ExpressionType.Call:
                                var tempExp = (currentExp as MethodCallExpression);
                                currentExp = tempExp.Object == null ? tempExp.Arguments.FirstOrDefault() : tempExp.Object;
                                continue;
                            case ExpressionType.MemberAccess:
                                currentExp = (currentExp as MemberExpression).Expression;
                                continue;
                        }
                        break;
                    }
                    while (stackExp.Any())
                    {
                        currentExp = stackExp.Pop();
                        if (currentExp.NodeType == ExpressionType.Call)
                        {
                            var methodCall = (currentExp as MethodCallExpression);
                            string methodName = methodCall.Method.Name;
                            if (methodName == "Queryable")
                            {
                                //子查询中用到的实体
                                var types = methodCall.Method.ReturnType.GenericTypeArguments;
                                //解析子查询表名
                                subquery.TableName = ClassMapper.Mapping(types[0])?.TableName;
                            }
                            else if (methodName == "Where")
                            {
                                var args = methodCall.Arguments;
                                string subQueryFirstName = ((args[0] as UnaryExpression).Operand as LambdaExpression).Parameters.First().Name;
                                string firstName = (_expression.MasterExpression as LambdaExpression).Parameters.First().Name;
                                var oldVal = _expression.IsPrefix;
                                _expression.IsPrefix = true;
                                _expression.IsSubquery = true;
                                subquery.Where = _expression.Resolve(args[0]);
                                //将子查询条件中表别名置空
                                subquery.Where = subquery.Where.Replace($"{subQueryFirstName}.", string.Empty);
                                //替换外层的表别名
                                subquery.Where = subquery.Where.Replace($"{firstName}.", "t1.");
                                _expression.IsPrefix = oldVal;
                                _expression.IsSubquery = false;
                            }
                            else if (methodName == "Select")
                            {
                                var args = methodCall.Arguments;
                                string subQueryFirstName = ((args[0] as UnaryExpression).Operand as LambdaExpression).Parameters.First().Name;
                                var oldVal = _expression.IsPrefix;
                                _expression.IsPrefix = true;
                                _expression.IsSubquery = true;
                                subquery.Select = _expression.Resolve(args[0]);
                                //将子查询条件中表别名置空
                                subquery.Select = subquery.Select.Replace($"{subQueryFirstName}.", string.Empty);
                                _expression.IsPrefix = oldVal;
                                _expression.IsSubquery = false;
                            }
                            else if (methodName == "TableName")
                            {
                                var args = methodCall.Arguments;
                                subquery.TableName = _expression.Resolve(args[0]);
                            }
                        }
                    }
                    return ToSql(subquery);
                default:
                    throw new Exception("unsupported expression");
            }
        }

        private string ToSql(SubqueryModel subquery)
        {
            if (subquery.IsIn.HasValue)
            {
                return ToSql3(subquery);
            }
            else if (subquery.IsExists.HasValue)
            {
                return ToSql2(subquery);
            }
            else if (!string.IsNullOrWhiteSpace(subquery.Select))
            {
                return ToSql1(subquery);
            }
            return "";
        }

        private string ToSql1(SubqueryModel subquery)
        {
            StringBuilder sql = new StringBuilder("(select");
            sql.Append($" {subquery.Select} from {subquery.TableName} where {subquery.Where}");
            if (subquery.Select.StartsWith("count(") 
                || subquery.Select.StartsWith("max(") 
                || subquery.Select.StartsWith("min(")
                || subquery.Select.StartsWith("avg("))
            {
                return sql.Append(")").ToString();
            }
            return ToSql(sql.ToString()) + ")";
        }

        private string ToSql2(SubqueryModel subquery)
        {
            StringBuilder sql = new StringBuilder(Convert.ToBoolean(subquery.IsExists) ? "exists" : "not exists");
            sql.Append($"(select 1 from {subquery.TableName} where {subquery.Where})");
            return sql.ToString();
        }

        private string ToSql3(SubqueryModel subquery)
        {
            StringBuilder sql = new StringBuilder(subquery.InField);
            sql.Append(Convert.ToBoolean(subquery.IsIn) ? " in" : " not in");
            sql.Append($"(select {subquery.Select} from {subquery.TableName} where {subquery.Where})");
            return sql.ToString();
        }

        private string ToSql(string sql)
        {
            switch (_expression.SqlDialect.GetType().Name.Replace("Dialect", string.Empty))
            {
                case "MySql":
                    return string.Concat(sql, " limit 1");
                case "SqlServer":
                    return sql.Insert(sql.IndexOf("select") + 6, " top 1 ");
                case "Oracle":
                    return string.Concat(sql, " and rownum=1");
            }
            return "";
        }
    }
}
